package com.suning.sawp.service.util;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;

/**
 * 
 * 
 * 功能描述： ftp 工具类
 * 
 * @author 作者 12072522@cnsuning.com
 * @created 2014-12-16 下午11:04:46
 * @version 1.0.0
 * @date 2014-12-16 下午11:04:46
 */
public class FtpClientUtil implements InitializingBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(FtpClientUtil.class);

    /**
     * 线程池,供上传使用
     */
    private static ThreadPoolExecutor executors = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(5000), Executors.defaultThreadFactory());

    /**
     * linux 系统文件分隔符
     */
    private static final String FILE_SPERATOR = "/";

    /**
     * ftp默认控制端口号
     */
    private static final int DEFAULT_PORT = 21;

    /**
     * 上传任务执行时间 单位 秒
     */
    private static long taskTimeOut = 2;

    /**
     * ftp服务器集群配置信息
     */
    private Set<FtpConfig> ftpConfigs = new HashSet<FtpConfig>();

    /**
     * 应用服务器上的临时文件存放目录
     */
    @Value("#{settingConfig['localTempFileDirPath']}")
    private String tmpdir;

    /**
     * ftp配置文件路径
     */
    private String configPath;

    private static final String ENCODING_UTF_8 = "UTF-8";

    public FtpClientUtil(String configPath) {
        this.configPath = configPath;
    }

    private void init() {
        loadConfig(configPath);
    }

    /**
     * 
     * 功能描述：载入ftp配置
     * 
     * @param 参数说明 返回值: 类型 <说明>
     * @return 返回值
     * @throw 异常描述
     * @see 需要参见的其它内容
     */
    private void loadConfig(String configPath) {

        InputStream inputStream = null;
        try {
            inputStream = getResource(configPath);
            if (null != inputStream) {
                parseXml(inputStream);
            }
        } catch (IOException e) {
            LOGGER.error("IOException!", e);
        } catch (DocumentException e) {
            LOGGER.error("SAXReader parse ftp xml error!", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    LOGGER.error("FtpClientUtil loadConfig failed!", e);
                }
            }
        }

    }

    /**
     * 
     * 功能描述：获取ftp配置
     * 
     * @param 参数说明 返回值: 类型 <说明>
     * @return 返回值
     * @throw 异常描述
     * @see 需要参见的其它内容
     */
    private InputStream getResource(String configPath) throws IOException {
        InputStream in = null;
        ClassLoader loader = FtpClientUtil.class.getClassLoader();
        if (loader != null) {
            in = loader.getResourceAsStream(configPath);
        }
        if (in != null) {
            return in;
        } else {
            // 换加载器
            return ClassLoader.getSystemResourceAsStream(configPath);
        }
    }

    /**
     * 
     * 功能描述：解析xml
     * 
     * @param 参数说明 返回值: 类型 <说明>
     * @return 返回值
     * @throws DocumentException
     * @throw 异常描述
     * @see 需要参见的其它内容
     */
    private void parseXml(InputStream in) throws DocumentException {
        Document doc = new SAXReader().read(in);
        Element root = doc.getRootElement();
        Element cluster = root.element("cluster");
        @SuppressWarnings("unchecked")
        List<Element> nodes = cluster.elements("node");
        for (Element node : nodes) {
            String host = node.elementTextTrim("host");
            int port = StringUtils.isBlank(node.elementTextTrim("port")) ? DEFAULT_PORT
                    : Integer.parseInt(node.elementTextTrim("port"));
            String userName = node.elementTextTrim("userName");
            String password = node.elementTextTrim("password");
            boolean master = Boolean.parseBoolean(node.elementTextTrim("master"));
            String domain = node.elementTextTrim("domain");
            FtpConfig ftpConfig = new FtpConfig(host, port, userName, password, master, domain);
            ftpConfigs.add(ftpConfig);
        }

    }

    /**
     * 
     * 功能描述：获取ftp客户端
     * 
     * @param 参数说明 返回值: 类型 <说明>
     * @return 返回值
     * @throws IOException
     * @throw 异常描述
     * @see 需要参见的其它内容
     */
    private FTPClient getClient(FtpConfig ftpConfig) throws IOException {
        FTPClient ftpClient = new FTPClient();
        try {
            ftpClient.connect(ftpConfig.getHost(), ftpConfig.getPort());
        } catch (UnknownHostException ex) {
            throw new IOException("Can't find FTP server '" + ftpConfig.getHost() + "'", ex);
        }
        int reply = ftpClient.getReplyCode();
        if (!FTPReply.isPositiveCompletion(reply)) {
            ftpClient.disconnect();
            throw new IOException("Can't connect to server '" + ftpConfig.getHost() + "'");
        }

        if (!ftpClient.login(ftpConfig.getUserName(), ftpConfig.getPassword())) {
            ftpClient.disconnect();
            throw new IOException("Can't login to server '" + ftpConfig.getHost() + "'");
        }
        // 字符编码
        ftpClient.setControlEncoding("UTF-8");
        // 设置文件类型
        ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
        // 使用ftp 被动模式
        ftpClient.enterLocalPassiveMode();
        return ftpClient;

    }

    @Override
    public void afterPropertiesSet() {
        init();
    }

    /**
     * 
     * 功能描述: 存储文件
     * 
     * @param ftpConfig ftp服务器配置
     * @param storeFileName 上传文件的文件名
     * @param in 存储文件的输入流
     * @param tarDir
     * @return
     * @throws IOException
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private boolean storeFile(final FtpConfig ftpConfig, String storeFileName, InputStream in, String tarDir)
            throws IOException {
        boolean flag;
        FTPClient ftpClient = getClient(ftpConfig);
        changeWorkingDiretory(ftpClient, tarDir);
        InputStream bufIn = null;
        try {
            bufIn = new BufferedInputStream(in);
            flag = ftpClient.storeFile(storeFileName, bufIn);
            if (!flag) {
                LOGGER.warn("Can't upload file '" + storeFileName + "' to FTP server. Check FTP permissions and path.");
            }
        } finally {
            try {
                if (null != bufIn) {
                    bufIn.close();
                }
                if (ftpClient.isConnected()) {
                    ftpClient.disconnect();
                }
            } catch (IOException ex) {
                LOGGER.error("close in exception!", ex);
            }
        }
        return flag;
    }

    /**
     * 全路径
     * 
     * @param relativeDir
     * @param fileName
     * @return
     */
    private String getFileUrl(String relativeDir, String fileName) {
        StringBuilder url = new StringBuilder(getRandomConfig().getDomain());
        url.append(FILE_SPERATOR).append(relativeDir).append(fileName).append("?version=")
                .append(System.currentTimeMillis());
        return url.toString();
    }

    /**
     * 
     * 功能描述：切换到工作目录
     * 
     * @param 参数说明 返回值: 类型 <说明>
     * @return 返回值
     * @throw 异常描述
     * @see 需要参见的其它内容
     */
    private void changeWorkingDiretory(FTPClient ftpClient, String tarPath) throws IOException {

        String[] subDirs = tarPath.split(FILE_SPERATOR);
        for (int i = 0; i < subDirs.length; i++) {
            if (!ftpClient.changeWorkingDirectory(subDirs[i])) {
                if (!ftpClient.makeDirectory(subDirs[i])) {
                    throw new IOException("ftp makeDirectory failed!");
                }
                if (!ftpClient.changeWorkingDirectory(subDirs[i])) {
                    throw new IOException("ftp changeWorkingDirectory failed!");
                }
            }
        }
    }

    /**
     * 
     * 功能描述: <br>
     * 〈功能详细描述〉
     * 
     * @param remote 远程文件路径
     * @param out
     * @return
     * @throws IOException
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public boolean download(String remote, OutputStream out) throws IOException {

        boolean flag = false;
        if (StringUtils.isBlank(remote) || null == out) {
            LOGGER.warn("'remote'or 'out' can not be null,please check you args!");
            return flag;
        }
        String relativePath = cuttingPath(remote);
        FTPClient ftpClient = getClient(getRandomConfig());

        try {
            flag = ftpClient.retrieveFile(relativePath, out);
        } finally {
            try {
                out.close();
                if (ftpClient.isConnected()) {
                    ftpClient.disconnect();
                }
            } catch (IOException ex) {
                LOGGER.error("FtpClientUtil downloadFile failed!", ex);
            }
        }
        return flag;

    }

    /**
     * 
     * 功能描述: <br>
     * 〈功能详细描述〉
     * 
     * @param remote 远程文件路径
     * @param out
     * @return
     * @throws IOException
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public boolean download(String remote, File file) throws IOException {

        boolean flag = false;
        if (StringUtils.isBlank(remote) || null == file || !file.exists()) {
            LOGGER.warn("'remote'or 'file' can not be null,please check you args!");
        } else {
            flag = download(remote, new FileOutputStream(file));
        }
        return flag;

    }

    /**
     * 
     * 功能描述: <br>
     * 裁剪路径
     * 
     * @param remote
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private String cuttingPath(String remote) {
        String cutedPath = remote;
        if (remote.indexOf("?version") != -1) {
            cutedPath = remote.substring(0, remote.indexOf("?version"));
        }
        return cutedPath;

    }

    /**
     * 
     * 功能描述: 获取随机配置
     * 
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private FtpConfig getRandomConfig() {
        int i = ftpConfigs.size();

        Random random = new Random();

        return ftpConfigs.toArray(new FtpConfig[] {})[random.nextInt(i)];

    }

    /**
     * 
     * 功能描述: <br>
     * 根据文件相对路径删除文件
     * 
     * @param filePath
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public boolean deleteFile(String remote) {
        boolean flag = true;
        if (StringUtils.isBlank(remote)) {
            flag = false;
            LOGGER.warn("FtpClientUtil deleteFile ,'remote'can not be null,please check you args!");
        } else {
            String relativePath = cuttingPath(remote);
            for (FtpConfig ftpConfig : ftpConfigs) {
                try {
                    FTPClient ftpClient = getClient(ftpConfig);
                    flag = ftpClient.deleteFile(relativePath);
                } catch (IOException e) {
                    LOGGER.error("FtpClientUtil deleteFile failed!", e);
                    flag = false;
                }
            }

        }
        return flag;
    }

    /**
     * 
     * 功能描述: 根据文件名获取文件类型
     * 
     * @param fileName
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private String getFileType(String fileName) {
        int index = fileName.lastIndexOf('.');
        if (-1 == index) {
            return "";
        } else {
            return fileName.substring(index, fileName.length());
        }

    }

    /**
     * 
     * 功能描述: <br>
     * 生成文件名 取32个字符的UUID,加上文件类型作为后缀
     * 
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private String generateFileName(String fileName) {
        StringBuilder sb = new StringBuilder();
        sb.append(UUID.randomUUID().toString().replace("-", "")).append(getFileType(fileName));
        return sb.toString();
    }

    /**
     * 
     * 功能描述: 上传文件
     * 
     * @param tarDir 目标目录
     * @param uploadFile
     * @param isRename 是否需要重命名文件 不重命名路径相同的时候会覆盖相同文件名的文件 true:系统生成文件名 false:使用fileName作为文件名
     * @param fileName isRename =true 可为null
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public String upload(final String tarDir, final File uploadFile, String fileName, boolean isRename) {
        String fileUrl = null;
        if (!uploadFile.exists()) {
            LOGGER.warn("Can't upload '" + uploadFile.getAbsolutePath() + "'. This file doesn't exist.");
            return fileUrl;
        }
        try {
            // 获取文件名文件名
            String tmpFileName;
            if (isRename) {
                tmpFileName = generateFileName(uploadFile.getName());
            } else {
                tmpFileName = fileName;
            }
            final String storeFileName = tmpFileName;

            // 并行上传
            CompletionService<Boolean> cs = new ExecutorCompletionService<Boolean>(executors);
            for (final FtpConfig ftpConfig : ftpConfigs) {

                cs.submit(new Callable<Boolean>() {
                    @Override
                    public Boolean call() throws Exception {
                        return storeFile(ftpConfig, storeFileName, new FileInputStream(uploadFile), tarDir);
                    }
                });
            }
            // 处理任务结果
            boolean flag = true;
            for (FtpConfig ftpConfig : ftpConfigs) {
                flag = cs.take().get(taskTimeOut, TimeUnit.SECONDS);
                if (!flag) {
                    LOGGER.warn("FtpClientUtil 文件上传失败>tarDir:{},storeFileName:{},ftp server:{}",
                            new Object[] { tarDir, uploadFile.getName(), ftpConfig.getHost() });
                    break;
                }
            }
            // 成功
            if (flag) {
                // 上传成功,拼装完整路径
                fileUrl = getFileUrl(tarDir, storeFileName);
            }

        } catch (Exception e) {
            LOGGER.error("FtpClientUtil upload failed!", e);
        }
        return fileUrl;
    }

    /**
     * 
     * 功能描述:上传文件，默认由系统生成随机文件名
     * 
     * @param tarDir
     * @param uploadFile
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public String upload(String tarDir, final File uploadFile) {

        return upload(tarDir, uploadFile, uploadFile.getName(), true);
    }

    /**
     * 
     * 功能描述：上传文件
     * 
     * @param tarDir 上传路径 fileName 上传文件名(例:子店铺id+head 或者店铺id+bg) in 上传文件输入流 返回值: 类型 <说明>
     * @return 返回值
     * @throw 异常描述
     * @see 需要参见的其它内容
     */
    public String upload(String tarDir, String fileName, InputStream in) {
        return upload(tarDir, fileName, in, true);
    }

    /**
     * 
     * 功能描述: <br>
     * 上传文件
     * 
     * @param tarDir
     * @param fileName
     * @param in
     * @param isRename 是否需要重命名文件 不重命名路径相同的时候会覆盖相同文件名的文件
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public String upload(String tarDir, String fileName, InputStream in, boolean isRename) {
        String fileUrl = null;
        if (StringUtils.isNotBlank(tarDir) && StringUtils.isNotBlank(fileName) && null != in) {
            File tmpFile = null;
            OutputStream out = null;
            try {
                tmpFile = File.createTempFile("fileUpload", getFileType(fileName), new File(tmpdir));
                out = new FileOutputStream(tmpFile);
                IOUtils.copy(in, out);
                fileUrl = upload(tarDir, tmpFile, fileName, isRename);
            } catch (Exception e) {
                LOGGER.error("FtpClientUtil upload failed!", e);
            } finally {
                IOUtils.closeQuietly(out);
                if (tmpFile != null) {
                    boolean delFlag = tmpFile.delete();
                    if (!delFlag) {
                        LOGGER.error("FtpClientUtil delete temp File Failed,file path:" + tmpFile.getAbsolutePath());
                    }
                }
                IOUtils.closeQuietly(in);
            }
        }
        return fileUrl;
    }

    /**
     * 
     * 功能描述:上传固定文件名的文件,保留之前版本的方法
     * 
     * @param tarDir
     * @param fileName
     * @param in
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public String uploadFixed(String tarDir, String fileName, InputStream in) {
        return upload(tarDir, fileName, in, false);
    }

    /**
     * 
     * 功能描述: 从主ftp服务器上下载文件
     * 
     * @param remote
     * @param file
     * @return
     * @throws IOException
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public boolean downloadFromMaster(String remote, File file) {

        boolean flag = false;
        if (StringUtils.isBlank(remote) || null == file || !file.exists()) {
            LOGGER.warn("'remote'or 'file' can not be null,please check you args!");
            throw new IllegalArgumentException("downloadFromMaster args can not be null!");
        }
        FtpConfig master = getMaster();
        if (null == master) {
            LOGGER.warn("ftp master server does not exist!");
        } else {
            FTPClient ftpClient = null;
            OutputStream out = null;
            try {
                ftpClient = getClient(master);
                out = new FileOutputStream(file);
                flag = ftpClient.retrieveFile(remote, out);
            } catch (Exception e) {
                LOGGER.error("FtpClientUtil downloadFromMaster failed!", e);
            } finally {
                try {
                    IOUtils.closeQuietly(out);
                    if (null != ftpClient && ftpClient.isConnected()) {
                        ftpClient.disconnect();
                    }
                } catch (IOException ex) {
                    LOGGER.error("FtpClientUtil downloadFromMaster close stream failed!", ex);
                }
            }
        }
        return flag;
    }

    /**
     * 
     * 功能描述: 获取主配置服务器
     * 
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private FtpConfig getMaster() {
        FtpConfig master = null;
        for (FtpConfig ftpConfig : ftpConfigs) {
            if (ftpConfig.isMaster()) {
                master = ftpConfig;
                break;
            }
        }
        return master;
    }

    /**
     * 
     * 功能描述: 检查文件是否在主ftp服务器上存在
     * 
     * @param remote
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public boolean checkFileExistsInMaster(String remote) {
        boolean flag = false;
        if (StringUtils.isBlank(remote)) {
            LOGGER.warn("'remote'can not be null,please check you args!");
            throw new IllegalArgumentException("checkFileExistsInMaster args can not be null!");
        }

        FtpConfig master = getMaster();

        if (null == master) {
            LOGGER.warn("ftp master server does not exist!");
        } else {
            FTPClient ftpClient = null;
            try {
                ftpClient = getClient(master);
                FTPFile[] file = ftpClient.listFiles(remote);
                if (file != null && file.length > 0) {
                    flag = true;
                }
            } catch (Exception e) {
                LOGGER.error("FtpClientUtil checkFileExistsInMaster failed!", e);
            } finally {
                try {
                    if (null != ftpClient && ftpClient.isConnected()) {
                        ftpClient.disconnect();
                    }
                } catch (IOException ex) {
                    LOGGER.error("FtpClientUtil checkFileExistsInMaster close connection failed!", ex);
                }
            }
        }

        return flag;
    }

    public Map<String, File> downLoadFiles(String host, String userName, String password, int port, String remotePath,
            String prefix, String endfix) throws IOException {
        Map<String, File> fileMap = new HashMap<String, File>();
        FTPClient ftpClient = new FTPClient();
        ftpClient.setControlEncoding(ENCODING_UTF_8);
        try {
            ftpClient.connect(host, port);
            ftpClient.login(userName, password);
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            FTPFile[] files = listFiles(ftpClient, remotePath);
            for (FTPFile ftpFile : files) {
                // 校验文件格式是是否符合条件
                if (checkFile(ftpFile, prefix, endfix)) {
                    continue;
                }
                String remoteFileName = ftpFile.getName();
                File file = new File(tmpdir + remoteFileName);
                boolean success = downLoadSingleFile(ftpClient, remoteFileName, file);
                if (success) {
                    fileMap.put(remoteFileName, file);
                }
            }

        } catch (SocketException e) {
            LOGGER.error("FtpClientUtil SocketException error!", e);
        } catch (IOException e) {
            LOGGER.error("FtpClientUtil IOException error!", e);
        } finally {
            if (ftpClient.isConnected()) {
                try {
                    ftpClient.disconnect();
                } catch (IOException e) {
                    LOGGER.error("FtpClientUtil IOException ftpClient.disconnect!", e);
                }
            }
        }
        return fileMap;
    }
    
    /**
     * 校验文件格式,
     * 功能描述: <br>
     * 〈功能详细描述〉
     *
     * @param ftpFile
     * @param prefix
     * @param endfix
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private boolean checkFile(FTPFile ftpFile, String prefix, String endfix) {
        // 不是文件类型
        if (!ftpFile.isFile()) {
            return false;
        } else if(StringUtils.isNotBlank(prefix) && !ftpFile.getName().startsWith(prefix)) {
            // 前缀不同
            return false;
        } else if (StringUtils.isNotBlank(endfix) && !ftpFile.getName().endsWith(endfix)) {
            // 后缀不同
            return false;
        }
        return true;
    }

    private boolean downLoadSingleFile(FTPClient ftpClient, String remote, File file) throws IOException {
        OutputStream os = new FileOutputStream(file);
        boolean ftpFlag = false;
        try {
            ftpFlag = ftpClient.retrieveFile(remote, os);
        } finally {
            os.close();
        }
        return ftpFlag;
    }

    private FTPFile[] listFiles(FTPClient ftpClient, String remote) throws IOException {
        String[] dirs = remote.split(FILE_SPERATOR);
        for (String dir : dirs) {
            if ("".equals(dir)) {
                dir = "/";
            }
            boolean dirFlag = ftpClient.changeWorkingDirectory(dir);
            if (!dirFlag && remote.endsWith(dir)) {
                return ftpClient.listFiles(dir);
            }

        }
        return ftpClient.listFiles();
    }
}
